#ifndef _NE_VECTOR3_H_
#define _NE_VECTOR3_H_

#include <mathematics/Vector2.h>

namespace ne
{
    class NEAPI Vector3
    {
        public:
            inline Vector3()
                : x(0.0f), y(0.0f), z(0.0f)
            {
            }

            inline explicit Vector3(const real v)
                : x(v), y(v), z(v)
            {
            }

            inline Vector3(const Vector3 &v)
                : x(v.x), y(v.y), z(v.z)
            {
            }

            inline explicit Vector3(const real* const v)
                : x(v[0]), y(v[1]), z(v[2])
            {
            }

            inline explicit Vector3(const Vector2 &v)
                : x(v.x), y(v.y), z(0.0f)
            {
            }

            inline Vector3(const real vx, const real vy, const real vz)
                : x(vx), y(vy), z(vz)
            {
            }

            inline void swap(Vector3 &v)
            {
                std::swap(x, v.x);
                std::swap(y, v.y);
                std::swap(z, v.z);
            }

            inline real operator [] (const size_t v) const
            {
                assert(v < 3);
                return *(&x + v);
            }

            inline real& operator [] (const size_t v)
            {
                assert(v < 3);
                return *(&x + v);
            }

            inline real* ptr()
            {
                return &x;
            }

            inline const real* ptr() const
            {
                return &x;
            }

            // Evaluate
            inline Vector3& operator = (const real v)
            {
                x = v;
                y = v;
                z = v;
                return *this;
            }

            inline Vector3& operator = (const Vector3 &v)
            {
                x = v.x;
                y = v.y;
                z = v.z;
                return *this;
            }

            // Equal
            inline bool operator == (const Vector3 &v) const
            {
                return (x == v.x) && (y == v.y) && (z == v.z);
            }

            // Inequivalence
            inline bool operator != (const Vector3 &v) const
            {
                return (x != v.x) || (y != v.y) || (z != v.z);
            }

            inline bool operator < (const Vector3 &v) const
            {
                return (x < v.x) && (y < v.y) && (z < v.z);
            }

            inline bool operator > (const Vector3 &v) const
            {
                return (x > v.x) && (y > v.y) && (z > v.z);
            }

            // Addition
            inline Vector3 operator + (const real v) const
            {
                return Vector3(x + v, y + v, z + v);
            }

            inline Vector3 operator + (const Vector3 &v) const
            {
                return Vector3(x + v.x, y + v.y, z + v.z);
            }

            NEAPI friend Vector3 operator + (const real vl, const Vector3 &vr);

            // Subtration
            inline Vector3 operator - (const real v) const
            {
                return Vector3(x - v, y - v, z - v);
            }

            inline Vector3 operator - (const Vector3 &v) const
            {
                return Vector3(x - v.x, y - v.y, z - v.z);
            }

            NEAPI friend Vector3 operator - (const real vl, const Vector3 &vr);

            // Multiplication
            inline Vector3 operator * (const real v) const
            {
                return Vector3(x * v, y * v, z * v);
            }

            inline Vector3 operator * (const Vector3 &v) const
            {
                return Vector3(x * v.x, y * v.y, z * v.z);
            }

            NEAPI friend Vector3 operator * (const real vl, const Vector3 &vr);

            // Division
            inline Vector3 operator / (const real v) const
            {
                assert(v != 0.0f);
                return Vector3(x / v, y / v, z / v);
            }

            inline Vector3 operator / (const Vector3 &v) const
            {
                assert((v.x != 0.0f) && (v.y != 0.0f) && (v.z != 0.0f));
                return Vector3(x / v.x, y / v.y, z / v.z);
            }

            NEAPI friend Vector3 operator / (const real vl, const Vector3 &vr);

            // Self updates
            inline Vector3& operator += (const real v)
            {
                x += v;
                y += v;
                z += v;
                return *this;
            }

            inline Vector3& operator += (const Vector3 &v)
            {
                x += v.x;
                y += v.y;
                z += v.z;
                return *this;
            }

            inline Vector3& operator -= (const real v)
            {
                x -= v;
                y -= v;
                z -= v;
                return *this;
            }

            inline Vector3& operator -= (const Vector3 &v)
            {
                x -= v.x;
                y -= v.y;
                z -= v.z;
                return *this;
            }

            inline Vector3& operator *= (const real v)
            {
                x *= v;
                y *= v;
                z *= v;
                return *this;
            }

            inline Vector3& operator *= (const Vector3 &v)
            {
                x *= v.x;
                y *= v.y;
                z *= v.z;
                return *this;
            }

            inline Vector3& operator /= (const real v)
            {
                assert(v != 0.0f);
                x /= v;
                y /= v;
                z /= v;
                return *this;
            }

            inline Vector3& operator /= (const Vector3 &v)
            {
                assert((v.x != 0.0f) && (v.y != 0.0f) && (v.z != 0.0f));
                x /= v.x;
                y /= v.y;
                z /= v.z;
                return *this;
            }

            // Signed
            inline const Vector3& operator + () const
            {
                return *this;
            }

            inline Vector3 operator - () const
            {
                return Vector3(-x, -y, -z);
            }

            inline real length() const
            {
                return std::sqrt((x * x) + (y * y) + (z * z));
            }

            inline real squaredLength() const
            {
                return (x * x) + (y * y) + (z * z);
            }

            // Get distance
            inline real distance(const Vector3 &v) const
            {
                return Vector3(x - v.x, y - v.y, z - v.z).length();
            }

            // Get square distance
            inline real squaredDistance(const Vector3 &v) const
            {
                return Vector3(x - v.x, y - v.y, z - v.z).squaredLength();
            }

            // Get a unit Vector3 with the same direction
            inline Vector3 getUnit() const
            {
                real len = length();
                if (len > 0.0f)
                {
                    real invLen = 1.0f / len;
                    return Vector3(x * invLen, y * invLen, z * invLen);
                }
                return ZERO;
            }

            // Normalize the Vector3
            inline real normalize()
            {
                real len = length();
                if (len > 0.0f)
                {
                    real invLen = 1.0f / len;
                    x *= invLen;
                    y *= invLen;
                    z *= invLen;
                }
                return len;
            }

            inline real dotProduct(const Vector3 &v) const
            {
                return (x * v.x) + (y * v.y) + (z * v.z);
            }

            inline real absDotProduct(const Vector3 &v) const
            {
                return std::abs(x * v.x) + std::abs(y * v.y) + std::abs(z * v.z);
            }

            inline Vector3 crossProduct(const Vector3 &v) const
            {
                return Vector3((y * v.z) - (z * v.y),
                               (z * v.x) - (x * v.z),
                               (x * v.y) - (y * v.x));
            }

            inline real angle(const Vector3 &v) const
            {
                real a = length();
                real b = v.length();
                if (a > 0.0f && b > 0.0f)
                {
                    return std::acos(dotProduct(v) / (a * b));
                }
                return 0.0f;
            }

            inline void makeFloor(const Vector3 &v)
            {
                if (x > v.x) x = v.x;
                if (y > v.y) y = v.y;
                if (z > v.z) z = v.z;
            }

            inline void makeCeil(const Vector3 &v)
            {
                if (x < v.x) x = v.x;
                if (y < v.y) y = v.y;
                if (z < v.z) z = v.z;
            }

            inline Vector3 perpendicular() const
            {
                Vector3 perp = crossProduct(UNIT_X);
                if (perp.squaredLength() == 0.0f)
                {
                    perp = crossProduct(UNIT_Y);
                    if (perp.squaredLength() == 0.0f)
                    {
                        perp = crossProduct(UNIT_Z);
                    }
                }
			    perp.normalize();
                return perp;
            }

            // TODO: n is normal
            inline Vector3 reflect(const Vector3 &n) const
            {
                return 2 * dotProduct(n) * n - *this;
            }

            // Function for writing to a stream.
            NEAPI friend std::ostream& operator << (std::ostream &o, const Vector3 &v);

            inline ~Vector3()
            {
            }

        public:
            static bool Equal(const Vector3 &vS, const Vector3 &vD, const real tolerance=Math::EPSILON);

            static Vector3 Mid(const Vector3 &vS, const Vector3 &vD);

            static Vector3 Lerp(const Vector3 &vS, const Vector3 &vD, const real u);

            static Vector3 Bezier(const Vector3 &vS, const Vector3 &vA, const Vector3 &vB, const Vector3 &vD, const real u);

        public:
            static const Vector3 ZERO;
            static const Vector3 ONE;
            static const Vector3 UNIT_X;
            static const Vector3 UNIT_Y;
            static const Vector3 UNIT_Z;
            static const Vector3 NEGATIVE_ONE;
            static const Vector3 NEGATIVE_UNIT_X;
            static const Vector3 NEGATIVE_UNIT_Y;
            static const Vector3 NEGATIVE_UNIT_Z;

        public:
            real x, y, z;
    };
}
#endif
